#include "PrecompiledHeaders.h"
#include "Ball.h"
#include "Constants.h"
#include "Utilities.h"
#include "PodServerInterface.h"
#include "Clone.h"
#include "DefaultPhysics.h"
#include "TeleportControl.h"

#define RAY_SIZE (3);

/*
 * Create Body in OgreNewt, initialise mass, material ID's etc
 */
Ball::Ball(OgreNewt::Body *b)
{
    body = b;
    body->setAutoFreeze(0); // when ball is exactly still, it 'freezes' which stops callbacks (like setting gravity)
                            // so it'll never move again. This tells it to never freeze so it will always move
    body->setCustomForceAndTorqueCallback<Ball>(&Ball::gravityCallback, this);
    body->setUserData(this);
    original_collision = body->getCollision();
    
    ogre_newt = body->getWorld();
    ogre_node = dynamic_cast<Ogre::SceneNode*>(body->getOgreNode());
    if (!ogre_node) throw new Ogre::Exception(80001, "Invalid node for ball - must be scene node", "Ball::Ball");
    ogre_node->setInitialState();
    
    initial_direction = Ogre::Vector3::NEGATIVE_UNIT_Z;
    Ogre::Node::ChildNodeIterator children = ogre_node->getChildIterator();
    
    while( children.hasMoreElements() )
    {
        Ogre::Node *child = children.getNext();
        Ogre::String name = child->getName();
        
        size_t i = name.find("dir");
        if (i != Ogre::String::npos) {
            initial_direction = child->_getDerivedPosition() - ogre_node->_getDerivedPosition();
            initial_direction.normalise();
            break;
        }
    }
    
    gravity_direction = Ogre::Vector3(0, -1, 0);
    gravity = gravity_direction * Constants::GRAVITY_STRENGTH;
    
    Ogre::Vector3 junk;
    body->getMassMatrix(mass, junk);
    default_mass = mass;
    
    default_size = size = body->getCollision()->getAABB().getHalfSize();
    size_factor = 1;
    
    total_time = previous_collision_time = 0.0;
    
    Ogre::SceneManager *scene_mgr = ogre_node->getCreator();
    
    Ogre::ParticleSystem *ps = scene_mgr->createParticleSystem(ogre_node->getName() + "_collision_ps", "spark");
    col_particles = ps->getEmitter(0);
    col_particles->setEmissionRate(0);
    
    col_particles_node = scene_mgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(0,2,0));
    col_particles_node->attachObject(ps);
    
    tele_exp_particles = scene_mgr->createParticleSystem(ogre_node->getName() + "_teleport_explosion_ps", "BallTeleport/Explosion");
    tele_exp_particles->getEmitter(0)->setEmissionRate(0);  
    tele_exp_particles->getEmitter(1)->setEmissionRate(0);
    
    tele_flash_particles = scene_mgr->createParticleSystem(ogre_node->getName() + "_teleport_flash_ps", "BallTeleport/Flash");
    tele_flash_particles->getEmitter(0)->setEmissionRate(0);
    
    tele_particles_node = scene_mgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(0,2,0));
    tele_particles_node->attachObject(tele_exp_particles);
    tele_particles_node->attachObject(tele_flash_particles);
    
    
    goal_exp_particles = scene_mgr->createParticleSystem(ogre_node->getName() + "_goal_explosion_ps", "BallTeleportEnd/Explosion");
    goal_exp_particles->getEmitter(0)->setEmissionRate(0);  
    
    goal_flash_particles = scene_mgr->createParticleSystem(ogre_node->getName() + "_goal_flash_ps", "BallTeleport/Flash");
    goal_flash_particles->getEmitter(0)->setEmissionRate(0);
    
    goal_particles_node = scene_mgr->getRootSceneNode()->createChildSceneNode(Ogre::Vector3(0,2,0));
    goal_particles_node->attachObject(goal_exp_particles);
    goal_particles_node->attachObject(goal_flash_particles);
    
    apply_force = true;
    teleporting = false;
    animating = false;
    
    last_floor_contact_time = last_downward_simulation_time = previous_collision_time = 0.0;
    in_contact = false;
}

Ball::~Ball(void)
{
    //body->removeForceAndTorqueCallback();
    //delete material_id;
    //delete mat_pair;
    
}

/*
 * Set the gravity on the ball. Also normalise to obtain the direction
 */
void Ball::setGravity(Ogre::Vector3 g)
{
 gravity_direction = gravity = g;
    gravity_direction.normalise();
}

/*
 * Reset ball's position and orientation so we can start playing again
 */
void Ball::reset()
{
    clearForces();
    body->unFreeze();
    body->setPositionOrientation( ogre_node->getInitialPosition(), ogre_node->getInitialOrientation() );
	body->setCollision(original_collision);
    if(body->getCollision() != original_collision) delete body->getCollision();
    size = default_size;
    ogre_node->setScale(size);
    ogre_node->setVisible(visible, false);
    play_state->homePod();
    if (animating) resetAnimation();
    apply_force = true;
    col_particles->setEmissionRate(0);
    
}

void Ball::clearForces()
{
    body->setVelocity(Ogre::Vector3::ZERO);
    body->setOmega(Ogre::Vector3::ZERO);
}

/*
 * Shoots rays in the direction the ball is travelling +/- slight offsets to predict future collisions
 */
void Ball::predictCollisions(float time)
{
    total_time += time;

    if(!apply_force) return;
    
    float WINDOW_SIZE = 0.2;
    // get ball's velocity, direction and speed
    Ogre::Vector3 ball_velocity = body->getVelocity(), ball_direction = ball_velocity;
    float ball_speed = ball_direction.normalise();
    // get a vector representing half the ball's size in the same direction as ball_direction
    Ogre::Vector3 half_ball = ball_direction * size.x;
    // get the position and orientation of the ball
    Ogre::Quaternion orient;
    Ogre::Vector3 pos;
    body->getPositionOrientation(pos, orient);
    
    // calculate the offsets to shoot by
    Ogre::Vector3 lr_offset = Ogre::Vector3::NEGATIVE_UNIT_Z.getRotationTo(ball_direction) * Ogre::Vector3(size.x / 2, 0, 0);
    Ogre::Vector3 ud_offset(0, size.x / 2, 0);
    
    Ogre::Vector3 start_point = pos + half_ball, end_point;
    OgreNewt::BasicRaycast *ray_casts[23];
    OgreNewt::BasicRaycast::BasicRaycastInfo ray_cast_info;
    bool got_info = false;
    
    // shoot a ray straight
    end_point = start_point + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[0] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    
    // shoot a ray slightly right
    end_point = start_point + lr_offset + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[1] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    
    // shoot a ray slightly left
    end_point = start_point - lr_offset + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[2] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    
    // shoot rays slightly up
    end_point = start_point + (0.1 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[3] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point + (0.2 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[4] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point + (0.3 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[5] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point + (0.4 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[6] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point + (0.5 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[7] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point + (0.6 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[8] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point + (0.7 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[9] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point + (0.8 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[10] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point + (0.9 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[11] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point + ud_offset + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[12] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    
    // shoot rays slightly down
    end_point = start_point - (0.1 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[13] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point - (0.2 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[14] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point - (0.3 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[15] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point - (0.4 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[16] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point - (0.5 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[17] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point - (0.6 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[18] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point - (0.7 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[19] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point - (0.8 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[20] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point - (0.9 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[21] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    end_point = start_point - ud_offset + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
    ray_casts[22] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
    
    // pick the closest collision, if there is one
    int i;
    for(i = 0; i < 23; i++)
    {
        if(ray_casts[i]->getHitCount() == 0) continue;
        BodyType bt(ray_casts[i]->getFirstHit().mBody->getType());
        if(bt.getSpecialType() != BodyType::SP_PICKUP &&
            (!got_info
                || ray_casts[i]->getFirstHit().mDistance < ray_cast_info.mDistance))
        {
            ray_cast_info = ray_casts[i]->getFirstHit();
        }
    }
    
    // if there was a collision and it hasn't been simulated already
    if(got_info)
    {
        if(previous_collision_time < total_time - (PodServerInterface::POD_MOVEMENT_DELAY - (WINDOW_SIZE / 2)))
        {
            previous_collision_time = total_time;
            last_floor_contact_time = total_time;
            play_state->simulateAcceleration(ray_cast_info.mNormal, ball_direction, ball_speed * -ball_direction.dotProduct(ray_cast_info.mNormal));
        }
        return;
    }
    
    if(!in_contact)
    {
        end_point = ogre_node->getPosition() - Ogre::Vector3(0, size.x * 1.1, 0);//start_point - (2.3 * ud_offset) + (ball_velocity * (PodServerInterface::POD_MOVEMENT_DELAY + (WINDOW_SIZE / 2)));
        ray_casts[1] = new OgreNewt::BasicRaycast(ogre_newt, start_point, end_point);
        if(ray_casts[1]->getHitCount() == 0)
        {
            float frac = (total_time - last_downward_simulation_time) / (total_time - last_floor_contact_time);
            if(sqrt(frac) > 0.2)
            {
                last_downward_simulation_time = total_time;
                play_state->addDownwardMovement();
            }
            return;
        }
        else last_floor_contact_time = total_time;
    }
    in_contact = false;
}

void Ball::teleport(Ogre::Vector3 destination, Ogre::Vector3 direction)
{
    // remember and clear forces
    teleport_destination = destination;
    teleport_direction = direction;
    
    previous_direction = body->getVelocity();
    previous_speed = previous_direction.normalise();
    previous_omega = body->getOmega();
    
    clearForces();
    
    // stop gravity being applied for animation
    apply_force = false;
    
    // do animation
    startAnimation(atTELEPORT_IN);
}

/*
 * Called at end of teleport_in animation
 */
void Ball::doTeleport()
{
    teleporting = true;
    //apply_force = true;
    
    // remove collision
    original_collision = body->getCollision();
    OgreNewt::Collision *col = new OgreNewt::CollisionPrimitives::Null(ogre_newt);
    body->setCollision(col);
    
    // set to be invisible
    dynamic_cast<Ogre::SceneNode*>(body->getOgreNode())->setVisible(false, false);
    
    // control player with teleport controller
    player->teleport(getPosition(), teleport_destination + Ogre::Vector3(0,size.y * 2, 0), teleport_direction);
}

void Ball::finishedTeleporting()
{
    teleporting = false;
    
    apply_force = false;
    
    // set to be visible
    dynamic_cast<Ogre::SceneNode*>(body->getOgreNode())->setVisible(visible, false);
    
    startAnimation(atTELEPORT_OUT);
}

void Ball::restartAfterTeleport()
{
    // remake collision
    delete body->getCollision();
    body->setCollision(original_collision);
    
    apply_force = true;
    
    // set velocity, position etc
    body->setVelocity(previous_speed * teleport_direction);
    //body->setOmega(previous_omega);
    
    player->getTeleportControl()->finishedTeleporting();
}

/*
 * OgreNewt callback to set the force (gravity) on the ball.
 */
void Ball::gravityCallback( OgreNewt::Body* me )
{
    //if(play_state->getPlayDuration() < 0.1)
    fprintf(PlayState::out_file, "Balls force %f %f %f\n Ball's position is %f %f %f\n", gravity.x, gravity.y, gravity.z, getPosition().x, getPosition().y, getPosition().z); // replay debuffing info
    if (apply_force)
        me->setForce(gravity * mass);
}

/*
 * Filters out unwanted collisions
 */
 /*
int Ball::userBegin()
{
    return DefaultPhysics::CollisionFilter(m_body0, m_body1);
}*/

/*
 * Gets called every time the ball collides with something.
 */
void Ball::collision( Ogre::Vector3 &pos, Ogre::Vector3 &norm)
{
    in_contact = true;
    Ogre::Vector3 ball_direction = body->getVelocity();
    Ogre::Real force = ball_direction.normalise();
    norm.normalise();
    // if angle between normal and ball's direction < just under 90 degrees (ie not the floor)
    float dot = norm.dotProduct(ball_direction);
    //printf("%f %f %f\n", norm.x, norm.y, norm.z);
    if(dot > 0.1 || dot < -0.1) {
        last_collision_time = play_state->getPlayDuration();
        play_state->ballCollision(body->getVelocity().length());
        col_particles->setEmissionRate(force * 5);
        col_particles->setDirection(norm);
        col_particles_node->setPosition(pos);
    }
    if (play_state->getPlayDuration() > last_collision_time + 0.05)
    {
        col_particles->setEmissionRate(0);
    }
}

void Ball::goalCollision()
{
    if (body->getVelocity().length() < Constants::END_ZONE_MAX_SPEED && !(animating && anim_type == atGOAL_IN)) {
        // stop gravity being applied for animation
        clearForces();
        apply_force = false;
        // do animation
        startAnimation(atGOAL_IN);
    }
}
/*
int Ball::userProcess()
{
    int type0 = m_body0->getType() % BODY_TYPE_COUNT;
    int type1 = m_body1->getType() % BODY_TYPE_COUNT;
    OgreNewt::Body *ball = NULL;
    OgreNewt::Body *other = NULL;
    int other_type;
    if(type0 == BT_BALL) {
        ball = m_body0;
        other = m_body1;
        other_type = type1;
    } else if (type1 == BT_BALL) {
        ball = m_body1;
        other = m_body0;
        other_type = type0;
    }
    if(ball != NULL)
    {   
        Ogre::Vector3 pos, norm, ball_direction = body->getVelocity();
        Ogre::Real force = ball_direction.normalise();
        getContactPositionAndNormal(pos, norm);
        norm.normalise();
        // if angle between normal and ball's direction < just under 90 degrees (ie not the floor)
        float dot = norm.dotProduct(ball_direction);
        if(dot > 0.1 || dot < -0.1) {
            last_collision_time = play_state->getPlayDuration();
            play_state->ballCollision(body->getVelocity().length());
            col_particles->setEmissionRate(force * 5);
            col_particles->setDirection(norm);
            col_particles_node->setPosition(pos);
        }
        if (play_state->getPlayDuration() > last_collision_time + 0.05)
        {
            col_particles->setEmissionRate(0);
        }
        
        if (other_type == BT_GOAL)
        {
            if (ball->getVelocity().length() < Constants::END_ZONE_MAX_SPEED && !(animating && anim_type == atGOAL_IN)) {
                
                // stop gravity being applied for animation
                clearForces();
                apply_force = false;
                // do animation
                startAnimation(atGOAL_IN);
                
            
            }
        }
        
    }
    
	return 1;
}
*/
void Ball::setPlayState(PlayState *ps)
{
    play_state = ps;
}

Ball *Ball::clone(Ogre::SceneManager *scene_mgr, Ogre::String suffix, int new_player_id)
{
    //clone scene node & body
    OgreNewt::Body *new_body = OgreNewt::cloneBody(body, suffix, new_player_id);
    return new Ball(new_body);
}

void Ball::setPosition(Ogre::Vector3 position)
{
    Ogre::Quaternion orient = ogre_node->getOrientation();
    body->setPositionOrientation(position, orient);
}

void Ball::refreshSize()
{
    OgreNewt::Collision *col = new OgreNewt::CollisionPrimitives::Ellipsoid(ogre_newt, size);
    body->setCollision(col);
    
    Ogre::Vector3 inertia = OgreNewt::MomentOfInertia::CalcEllipsoidSolid(mass, size);
    body->setMassMatrix(mass, inertia);
    
    ogre_node->setScale(size);
    
    play_state->getResources()->first_person_camera->setBallSize(size.x);
}

void Ball::increaseSize()
{

    Ogre::Vector3 pos, new_size;
    Ogre::Quaternion orient;
    
    size_factor *= 1.5;
    new_size = default_size * size_factor;
    
    body->getPositionOrientation(pos, orient);
    body->setPositionOrientation(pos + (new_size - size), orient);
    
    
    size = new_size;
    mass = default_mass * size_factor;
    
    refreshSize();
}

void Ball::decreaseSize()
{
    size_factor /= 1.5;
    size = default_size * size_factor;
    mass = default_mass * size_factor;
    
    refreshSize();
}

void Ball::startAnimation(BallAnimType t)
{
    animating = true;
    anim_type = t;
    anim_state = 0;
    anim_time = 0;
}

void Ball::resetAnimation()
{
    switch (anim_type)
    {
        case atTELEPORT_IN:
        case atTELEPORT_OUT:
            tele_exp_particles->getEmitter(0)->setEmissionRate(0);
            tele_exp_particles->getEmitter(1)->setEmissionRate(0);
            tele_flash_particles->getEmitter(0)->setEmissionRate(0);
            break;
        case atGOAL_IN:
            goal_exp_particles->getEmitter(0)->setEmissionRate(0);
            goal_flash_particles->getEmitter(0)->setEmissionRate(0);
        case atSIZE_INCREASE:
            //animateSizeIncrease(evt.timeSinceLastFrame);
            break;
        case atSIZE_DECREASE:
            //animateSizeDecrease(evt.timeSinceLastFrame);
            break;
    }

    animating = false;
}

bool Ball::frameStarted(Ogre::Real time)
{
    Ogre::Vector3 pos;
    Ogre::Quaternion orient;
    body->getPositionOrientation(pos, orient);
    fprintf(PlayState::out_file, "ball position %f %f %f\n", pos.x, pos.y, pos.z);
    fprintf(PlayState::out_file, "ball velocity %f %f %f\n", body->getVelocity().x, body->getVelocity().y, body->getVelocity().z);
    fprintf(PlayState::out_file, "ball omega %f %f %f\n", body->getOmega().x, body->getOmega().y, body->getOmega().z);

    if (animating) {
        anim_time += time;
        switch (anim_type)
        {
            case atTELEPORT_IN:
                animateTeleportIn();
                break;
            case atTELEPORT_OUT:
                animateTeleportOut();
                break;
            case atGOAL_IN:
                animateGoalIn();
            case atSIZE_INCREASE:
                //animateSizeIncrease(evt.timeSinceLastFrame);
                break;
            case atSIZE_DECREASE:
                //animateSizeDecrease(evt.timeSinceLastFrame);
                break;
        }
    }
    return true;
}

void Ball::animateTeleportIn()
{
    Ogre::Real s_factor;
    Ogre::Real p_factor;
    
    switch (anim_state)
    {
        case 0:
            tele_particles_node->setPosition(ogre_node->getPosition());
            anim_time = 0;
            anim_state++;
			play_state->playSound(AUDIO_TELEPORT_IN);
        break;
        
        case 1:
            if (anim_time < 0.2) {
                Ogre::Real time_factor = (anim_time / 0.2);
                s_factor = 1 - time_factor; 
                p_factor = (time_factor < 0.5) ? time_factor * 2 : (1-time_factor) * 2;
            } else {
                s_factor = p_factor = 0;
                anim_time = 0;
                anim_state++;
            }
            
            tele_exp_particles->getEmitter(0)->setEmissionRate(p_factor * 200);
            tele_exp_particles->getEmitter(1)->setEmissionRate(p_factor * 200);
            ogre_node->setScale(size * s_factor);
        break;
        
        case 2:
            if (anim_time < 0.3) {
                Ogre::Real time_factor = (anim_time / 0.3);
                p_factor = (time_factor < 0.5) ? time_factor * 2 : (1-time_factor) * 2;
                tele_flash_particles->getEmitter(0)->setEmissionRate(p_factor * 25);
            } else {
                tele_flash_particles->getEmitter(0)->setEmissionRate(0);
                anim_state++;
            }
        break;
        
        case 3:
            animating = false;
            ogre_node->setScale(size);
            doTeleport();
        break;
    }
}

void Ball::animateGoalIn()
{
	if (!animating) return;
    Ogre::Real s_factor;
    Ogre::Real p_factor;
    
    switch (anim_state)
    {
        case 0:
            goal_particles_node->setPosition(ogre_node->getPosition());
            anim_time = 0;
			play_state->playSound(AUDIO_ENDZONE);
            anim_state++;
        break;
        
        case 1:
            if (anim_time < 0.2) {
                Ogre::Real time_factor = (anim_time / 0.2);
                s_factor = 1 - time_factor; 
                p_factor = (time_factor < 0.5) ? time_factor * 2 : (1-time_factor) * 2;
            } else {
                s_factor = p_factor = 0;
                anim_time = 0;
                anim_state++;
            }
            
            goal_exp_particles->getEmitter(0)->setEmissionRate(p_factor * 200);
            ogre_node->setScale(size * s_factor);
        break;
        
        case 2:
            if (anim_time < 0.3) {
                Ogre::Real time_factor = (anim_time / 0.3);
                p_factor = (time_factor < 0.5) ? time_factor * 2 : (1-time_factor) * 2;
                goal_flash_particles->getEmitter(0)->setEmissionRate(p_factor * 25);
            } else {
                goal_flash_particles->getEmitter(0)->setEmissionRate(0);
                anim_state++;
            }
        break;
        
        case 3:
            animating = false;
            player->finished();
            player->getSession()->setState(ssSUCCESSFUL);
            original_collision = body->getCollision();
            body->setCollision(new OgreNewt::CollisionPrimitives::Null(body->getWorld()));
            ogre_node->setScale(size);
            ogre_node->setVisible(false, false);
            apply_force = true;
        break;
    }
}

void Ball::animateTeleportOut()
{
Ogre::Real s_factor;
    Ogre::Real p_factor;
    
    switch (anim_state)
    {
    
        case 0:
            tele_particles_node->setPosition(ogre_node->getPosition());
            ogre_node->setScale(Ogre::Vector3::ZERO);
            anim_time = 0;
            anim_state++;
			play_state->playSound(AUDIO_TELEPORT_OUT);
        break;
        
        case 1:
            if (anim_time < 0.3) {
                Ogre::Real time_factor = (anim_time / 0.3);
                p_factor = (time_factor < 0.5) ? time_factor * 2 : (1-time_factor) * 2;
                tele_flash_particles->getEmitter(0)->setEmissionRate(p_factor * 25);
            } else {
                tele_flash_particles->getEmitter(0)->setEmissionRate(0);
                anim_time = 0;
                anim_state++;
            }
        break;
            
        case 2:
            if (anim_time < 0.2) {
                Ogre::Real time_factor = (anim_time / 0.2);
                s_factor = time_factor; 
                p_factor = (time_factor < 0.5) ? time_factor * 2 : (1-time_factor) * 2;
            } else {
                s_factor = 1;
                p_factor = 0;
                anim_state++;
            }
            
            tele_exp_particles->getEmitter(0)->setEmissionRate(p_factor * 200);
            tele_exp_particles->getEmitter(1)->setEmissionRate(p_factor * 200);
            ogre_node->setScale(size * s_factor);
        break;
        
        case 3:
            animating = false;
            ogre_node->setScale(size);
            restartAfterTeleport();
        break;
    }
}

void Ball::setVisibility(bool visible)
{
    this->visible = visible;
    ogre_node->setVisible(visible, false);
}
